package org.nightstudio.common.source.spi;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.ObjectDeletedException;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Mappings;
import org.hibernate.cfg.NamingStrategy;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.dialect.Dialect;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import org.nightstudio.common.bean.Pagination;
import org.nightstudio.common.util.constant.SpringConstant;
import org.nightstudio.common.util.exception.sys.DataBaseOperationException;
import org.nightstudio.common.util.exception.sys.NightStudioException;
import org.nightstudio.common.util.exception.sys.ParameterException;
import org.nightstudio.common.util.exception.sys.SystemInternalException;
import org.nightstudio.common.util.lang.FieldTypeFilter;
import org.nightstudio.common.util.lang.ObjectUtil;
import org.nightstudio.common.util.spring.SpringUtil;
import org.nightstudio.common.util.string.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class NightStudioHibernateDaoSupport<T> extends HibernateDaoSupport
		implements IDao<T> {
	private static Logger logger = Logger.getLogger(NightStudioHibernateDaoSupport.class);
	private static final String TOP_BEAN_PACKAGE = "org.nightstudio.packagetemplate.bean";
	private static final String REF_ATTRIBUTE = "REF";
	private static final String ID_ATTRIBUTE = "PROP_ID";
	@SuppressWarnings("unchecked")
	protected Class<T> clazz = (Class<T>) ObjectUtil.getActualTypeArguments(this.getClass(), 0);
	
	public Serializable save(T entity) throws NightStudioException {
		if (entity == null) {
			logger.error("entity is null");
			throw new ParameterException();
		}
		try {
			return this.getHibernateTemplate().save(entity);
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	public void deleteOne(Serializable id) throws NightStudioException {
		try {
			String refAttribute = (String) ObjectUtil.getFieldValue(clazz, REF_ATTRIBUTE);
			String idAttribute = (String) ObjectUtil.getFieldValue(clazz, ID_ATTRIBUTE);
			Query query = this.createQuery("delete " + refAttribute + " where " + idAttribute + " = :id");
			query.setParameter("id", id);
			query.executeUpdate();
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	public void deleteOneByExample(T entity) throws NightStudioException {
		if (entity == null) {
			logger.error("entity is null");
			throw new ParameterException();
		}
		try {
			this.getHibernateTemplate().delete(entity);
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public T update(T entity) throws NightStudioException {
		if (entity == null) {
			logger.error("entity is null");
			throw new ParameterException();
		}
		try {
			return (T) this.getHibernateTemplate().merge(entity);
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<T> getAll() throws NightStudioException {
		try {
			return this.createCriteria().list();
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	public List<T> getAll(Pagination pagination) throws NightStudioException {
		Criteria criteria = this.createCriteria();
		return this.getSomeByPagination(criteria, pagination);
	}
	
	@SuppressWarnings("unchecked")
	public List<T> getSomeByExample(T exampleEntity) throws NightStudioException {
		if (exampleEntity == null) {
			logger.error("example is null");
			throw new ParameterException();
		}
		try {
			Criteria criteria = this.createCriteria();
			criteria.add(Example.create(exampleEntity));
			extendCriteria(criteria, exampleEntity);
			return criteria.list();
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	public List<T> getSomeByExample(T exampleEntity, Pagination pagination) throws NightStudioException {
		if (exampleEntity == null) {
			logger.error("example is null");
			throw new ParameterException();
		}
		try {
			Criteria criteria = this.createCriteria();
			criteria.add(Example.create(exampleEntity));
			extendCriteria(criteria, exampleEntity);
			return this.getSomeByPagination(criteria, pagination);
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public T getOne(Serializable id) throws NightStudioException {
		if (id == null) {
			logger.error("id is null");
			throw new ParameterException();
		}
		try {
			return (T) this.getHibernateTemplate().get(clazz, id);
		} catch (ObjectDeletedException e) {
			return null;
		} catch (InvalidDataAccessApiUsageException e) {
			return null;
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public T getOneByExample(final T exampleEntity) throws NightStudioException {
		if (exampleEntity == null) {
			logger.error("example is null");
			throw new ParameterException();
		}
		try {
			Criteria criteria = this.createCriteria();
			criteria.add(Example.create(exampleEntity));
			extendCriteria(criteria, exampleEntity);
			List<T> list = criteria.list();
			if (list != null && list.size() > 0) {
				return list.get(0);
			} else {
				return null;
			}
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<T> getSomeByPagination(Criteria criteria, Pagination pagination) throws NightStudioException {
		if (criteria == null) {
			logger.error("criteria is null");
			throw new ParameterException();
		}
		if (pagination == null) {
			logger.error("pagination is null");
			throw new ParameterException();
		}
		if (pagination.isInit()) {
			criteria.setFirstResult((int) pagination.getFirstResult());
			criteria.setMaxResults(pagination.getPageSize());
			return criteria.list();
		} else {
			if (criteria instanceof CriteriaImpl) {
				CriteriaImpl criteriaImpl = (CriteriaImpl) criteria;
				Projection origProjection = criteriaImpl.getProjection();
				ProjectionList projectionList = Projections.projectionList()
						.add(Projections.rowCount());
				if (origProjection != null) {
					projectionList.add(origProjection);
				}
				Projection newProjection = projectionList;
				long rowCount = ((Integer) criteria.setProjection(newProjection)
						.uniqueResult()).longValue();
				pagination.setRowCount(rowCount);
				if (origProjection != null) {
					criteria.setProjection(origProjection);
				} else {
					criteria.setProjection(null);
					criteria.setResultTransformer(Criteria.ROOT_ENTITY);
				}
				criteria.setFirstResult((int) pagination.getFirstResult());
				criteria.setMaxResults(pagination.getPageSize());
				return criteria.list();
			} else {
				logger.error("criteria isn't CriteriaImpl");
				throw new SystemInternalException();
			}
		}
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void extendCriteria(Criteria criteria, Object obj) throws HibernateException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NightStudioException {
		if (criteria == null) {
			logger.error("criteria is null");
			throw new ParameterException();
		}
		final String idFieldName = this.getLogicalIdentityColumnName();
		List<Field> fields = ObjectUtil.getFields(obj, new FieldTypeFilter () {
			public boolean doFilter(Field field) {
				if (field.getName().equals(idFieldName)) {
					return false;
				}
				if (field.getType().getName() != null && Set.class.getName().equals(field.getType().getName())) {
					return true;
				}
				if (field.getType().getPackage() != null && field.getType().getPackage().getName().startsWith(TOP_BEAN_PACKAGE)) {
					return true;
				}
				return false;
			}
		});
		Object value;
		Set setValue;
		Iterator it;
		List list;
		String alias;
		if (fields != null) {
			for (Field field : fields) {
				if (!field.getType().equals(Set.class)) {
					if ((value = ObjectUtil.getFieldValue(obj, field)) != null) {
						alias = getHbmNameFromLogicalColumnName(field.getName());
						criteria.createAlias(alias, alias);
						criteria.add(Restrictions.eq(alias + "." + idFieldName, ObjectUtil.getFieldValue(value, idFieldName)));
					}
				} else {
					if ((setValue = (Set) ObjectUtil.getFieldValue(obj, field)) != null) {
						alias = getHbmNameFromLogicalColumnName(field.getName());
						criteria.createAlias(alias, alias);
						list = new ArrayList(setValue.size());
						it = setValue.iterator();
						while (it.hasNext()) {
							list.add(ObjectUtil.getFieldValue(it.next(), idFieldName));
						}
						criteria.add(Restrictions.in(alias + "." + idFieldName, list));
					}
				}
			}
		}
	}
	
	protected Criteria createCriteria() {
		return this.getSession().createCriteria(clazz);
	}
	
	protected Criteria createCriteriaByExample(final T exampleEntity) throws NightStudioException {
		Criteria criteria = this.createCriteria();
		criteria.add(Example.create(exampleEntity));
		try {
			this.extendCriteria(criteria, exampleEntity);
		} catch (Throwable e) {
			throw handleThrowable(e);
		}
		return criteria;
	}
	
	protected Query createQuery(String query) {
		return this.getSession().createQuery(query);
	}
	
	protected static NightStudioException handleThrowable(Throwable e) {
		try {
			logger.error("", e);
			throw e;
		} catch (DataAccessException e1) {
			return new DataBaseOperationException();
		} catch (HibernateException e1) {
			return new DataBaseOperationException();
		} catch (IllegalArgumentException e1) {
			return new DataBaseOperationException();
		} catch (NoSuchMethodException e1) {
			return new DataBaseOperationException();
		} catch (InvocationTargetException e1) {
			return new DataBaseOperationException();
		} catch (IllegalAccessException e1) {
			return new DataBaseOperationException();
		} catch (IllegalStateException e1) {
			return new DataBaseOperationException();
		} catch (SQLException e1) {
			return new DataBaseOperationException();
		} catch (NightStudioException e1) {
			return e1;
		} catch (Throwable e1) {
			logger.error("uncatched exception", e1);
			return new SystemInternalException();
		}
	}
	
	@Autowired
	protected void setMySessionFactory(SessionFactory sessionFactory) {
		super.setSessionFactory(sessionFactory);
	}
	
	protected LocalSessionFactoryBean getLocalSessionFactoryBean() throws NightStudioException {
		return ((LocalSessionFactoryBean) SpringUtil.getBean("&" + SpringConstant.SESSION_FACTORY_BEAN_ID));
	}
	
	protected Dialect getDialect(Configuration config) {
		return Dialect.getDialect(config.getProperties());
	}
	
	protected PersistentClass getClassMapping() throws NightStudioException {
		return this.getConfiguration().getClassMapping(clazz.getName());
	}
	
	protected Table getTable() throws NightStudioException {
		return this.getClassMapping().getTable();
	}
	
	protected Configuration getConfiguration() throws NightStudioException {
		return this.getLocalSessionFactoryBean().getConfiguration();
	}
	
	protected NamingStrategy getNamingStrategy() throws NightStudioException {
		return this.getConfiguration().getNamingStrategy();
	}
	
	private Mappings mappings = null;
	protected synchronized Mappings getConfigurationMappings() throws NightStudioException {
		if (mappings == null) {
			mappings = this.getConfiguration().createMappings();
		}
		return mappings;
	}
	
	protected List<String> generateSchemaCreationScript() throws NightStudioException {
		Configuration config = this.getConfiguration();
		String[] sqls = config.generateSchemaCreationScript(this.getDialect(config));
		List<String> sqlList = new ArrayList<String>(sqls.length);
		for (String sql : sqls) {
			sqlList.add(sql);
		}
		return sqlList;
	}
	
	protected String getLogicalIdentityColumnName () {
		try {
			return this.getLogicalColumnName(this.getClassMapping().getIdentifierProperty().getName());
		} catch (NightStudioException e) {
			logger.error("", e);
			return null;
		}
	}
	
	@SuppressWarnings("unchecked")
	protected String getPhysicalIdentityColumnName () {
		try {
			Iterator<Column> it = this.getClassMapping().getIdentifierProperty().getColumnIterator();
			if (it.hasNext()) {
				return it.next().getName();
			}
			return null;
		} catch (NightStudioException e) {
			logger.error("", e);
			return null;
		}
	}
	
	protected String getPhysicalColumnName(String hbmName) {
		String logicalColumnName = this.getLogicalColumnName(hbmName);
		return getPhysicalColumnNameFromLogicalColumnName(logicalColumnName);
	}
	
	protected String getPhysicalColumnNameFromLogicalColumnName(String logicalColumnName) {
		/*try {
			return this.getConfigurationMappings().getPhysicalColumnName(logicalColumnName, this.getTable(clazz.getName()));
		} catch (Throwable e) {
			logger.error("", e);
			try {
				return this.getNamingStrategy().columnName(logicalColumnName);
			} catch (MimsException e1) {
				logger.error("", e1);
				return null;
			}
		}*/
		return StringUtil.upperCaseToUnderLine(logicalColumnName);
	}
	
	protected String getLogicalColumnNameFromPhysicalName(String physicalName) {
		/*try {
			return this.getConfigurationMappings().getLogicalColumnName(physicalName, this.getTable(clazz.getName()));
		} catch (Throwable e) {
			logger.error("", e);
			return null;
		}*/
		return StringUtil.underLineToUpperCase(physicalName);
	}
	
	public String getLogicalColumnName(String hbmName) {
		/*try {
			return this.getNamingStrategy().logicalColumnName(hbmName, hbmName);
		} catch (Throwable e) {
			logger.error("", e);
			return null;
		}*/
		//return StringUtil.toLowerCaseInitial(hbmName);
		return hbmName;
	}
	
	protected String getHbmNameFromLogicalColumnName(String logicalColumnName) {
		return logicalColumnName;
	}
}
